热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《算法导论的Java实现》7堆排序

7堆排序7.1堆堆的概念不想多介绍,书上都有。这里只提几个基本概念,以说明后面的伪代码。堆可以看出一颗完全二叉树,除了最后一层,树的每一层都是填满的,最后一层,也是从左至右填。在堆排序里面,堆就用

7 堆排序

7.1 堆

堆的概念不想多介绍,书上都有。这里只提几个基本概念,以说明后面的伪代码。
堆可以看出一颗完全二叉树,除了最后一层,树的每一层都是填满的,最后一层,也是从左至右填。
在堆排序里面,堆就用数组表示,不需要其他的数据结构(因为是完全二叉树),观察后面伪代码,可以看出,对于完全二叉树,用3个函数(用C来实现的话是3个宏(macro)),可以确定任何一个节点的父子节点。
想强调的是,这里没有一个叫“堆”的数据结构,它只是个数组,堆(完全二叉树)存在于我们的脑子了,我们用本篇后面介绍的很多方法来操作这个很普通的数组,所有的方法的集合,就赋予了这个普通的数组以“堆”的属性。
类似的,如果有个双向链表(数组也可以),我们赋予它先进先出的存取方法集合,那么它就是个“队列”;如果我们赋予它先进后出的存取方法集合,那么它就是个“栈”。队列和栈本身没有与众不同的实体,它们只是普通的链表(或者数组),队列和栈是存在于我们脑子中的。所谓“运用之妙,存乎一心”(《宋史·岳飞传》)就在于此。


伪代码:




Java代码:

   
输出:
-1 1 2
0 3 4
0 5 6
1 7 8
1 9 10
2 11 12
2 13 14
3 15 16
3 17 18
4 19 20

上面输出的是对于每个节点(0到9)的父节点,左子节点,右子节点的数组下标。
根节点0的父节点是-1(伪代码里面是0)。
要说明的是:伪代码里面的乘2除2运算,我在Java里面都用了位移操作。这更符合堆排序的本意。在C和汇编里,位移运算比乘除要快得多。位移运算比加减更快,可能只占一个CPU时钟,乘除要占20多个以上,差了一个数量级。效率而言,那是差不起的。

7.2 保持堆的性质

伪代码:


Java代码:

  
输出:
5 6 4 6 1 3 2 2

这里如果不说明一下,可能有点理解困难,因为我的文章里面没有图,二叉树的图像必须存在于自己的脑子里。如果参考了《算法导论》里,这个章节的堆的二叉树图像的话,将容易理解得多。
我这个测试代码里,输入的是:5, 2, 4, 6, 1, 3, 2, 6
它的图像如下:

                5
         2            4
      6     1      3     2
    6

如果,你还没有理解堆是怎么一回事儿,那就就把上面这个二叉树,从上到下,从左到右,遍历一遍,别去管树的父子关系。从上到下,从左到右,遍历的结果,就是原来的输入:5, 2, 4, 6, 1, 3, 2, 6。也就是我前面说过的,数组还是那个数组,但是“存乎一心”的去想象,在脑子里,把数组的元素一个一个抽出,放到二叉树里面,第一行放一个(根),第二行放2个,第三行放4个,……,第n行放2^(n-1)个,放完为止,最下面一行,右面部分可以为空,别的地方都应该是满的,这就是堆(完全二叉树)。
然后,测试时,调用heapify函数,参数是1,也就是说对第二个元素进行重排,也就是上面二叉树图像里面第二行最左面的元素“2”进行重排。heapify里面,会把“2”和它的左右节点“6”和“1”进行比较,得出左节点“6”最大(比自己和右节点大)。就把自己(“2”)和左节点“6”进行交换,然后递归左节点(第4个元素,下标为3)。交换以后,原来的左节点(第4个元素,下标为3)的值是本身“2”,再一次(递归)判断自己和左右节点的关系,由于没有右节点,就只需要和左节点,最下层那个孤零零的“6”进行比较,然后交换。最后得出的图像如下:

                5
         6            4
      6     1      3     2
    2

上面的图像还原成数组,也就是我的程序的输出结果:5 6 4 6 1 3 2 2

堆排序里面,上面这个函数是最核心部分,它非常的漂亮(自我感觉Java程序比伪代码更加漂亮,呵呵),所以整个堆排序也是非常漂亮的一种排序。


7.3 建堆

伪代码:


Java代码:

   

输出:
6 6 4 5 1 3 2 2

有了前面的基础,把这个输出想象成一个二叉树,应该不太困难了:

                6
         6            4
      5     1      3     2
    2

上面就是一个建好的堆,它满足每个节点都比自己的子节点要大。
稍微要说明一下的是:for i ← ┕length[A]/2┙ downto 1
为什么从┕length[A]/2┙开始?因为我们不用重排叶子节点。
我的测试数组中,一共8个元素,也就是只要从第4个元素开始循环就可以了。大家可以验证一下,第4个元素也就是第3行的第一个元素,最后的结果是“5”的那个节点,它是最后一个有儿子的节点,从它往后,都是叶子节点了。为什么只要除以二再向下取整,为什么会如此简洁?一切的起因就是:它是一颗二叉树,每一层的个数是2^(n-1),全满的时候,总数是2^n个。


7.4 堆排序算法

伪代码:


对已经建立好的堆,运行上面的伪代码后,就可以得到,排好顺序的数组了。
但是,直接看上面的伪代码,并不是很容易理解,那就要仔细看一下《算法导论》的原文了。
我这里简单介绍一下思路:第一次建立好堆之后,第一个元素(根)就是我们要的排好序的第一个元素;把这个元素和最后一个元素进行交换(也就是抽出),然后调用HEAPIFY函数,进行重组堆。
这里要注意:重组堆重组所有的数组元素,而是重组从第一元素开始到没有被抽出的元素为止。这里要用到伪代码的heap-size[A],它看上去像一个函数,但其实它只是个变量。之所以,我前面的Java实现都没有管这个变量,那是因为一直没有用到,建堆时,是把整个数组都当成一个堆的,不需把后面若干元素排除在外。
现在,我们需要这个heap-size[A]变量了,它为我们指出,当前堆的最后一个元素,而这个元素之后的都是已经被抽出,已经排序好的元素。
下面的Java实现里,会重载(overload)heapify这个函数,没有搞清什么叫重载(overload),什么叫重写(override)和多态(polymorphism)的,要自己回家好好看书。

Java代码:



输出:
1 2 2 3 4 5 6 6

前面,我给出的都是和伪代码相对应的Java代码片段。而直到这里,我把所有的代码都给出了,因为我们的堆排序已经完成了。

coding后感
这次的感想不多,因为很多要说的,在上面各段伪代码说明时都说了。
之所以针对每段伪代码都做了说明,是因为堆排序不如别的排序(插入,选择,冒泡)那么直观,需要动脑筋想象二叉树。
数组下标的差异,在堆排序里,没有造成太大的困扰,甚至于,因为Java数组下标从零开始,反而使Java的代码更加的漂亮。
堆排序的程序量其实不多,但是heapify,buildHeap,heapSort这3个函数各司其职,配合的丝丝入扣。

7.5 优先级队列

伪代码:



伪代码:





code后感
上面的伪代码,我写了一点点就放弃了,所以没有Java代码提供给大家。因为实在没有太大的意思。

优先级队列是一个堆的应用,堆排序完成后,原来的代码上增加堆的追加和删除操作后,可以形成一个实用的优先级队列。大学三年级时,学操作系统时,我们都做过作业,自己做个小型的分时系统,进程调度管理,我本来准备参考windows的,因为linux的程序是公开,同学大多抄袭linux代码,而我想干点有难度的,就去反编译windows。后来发现,就内核而言,windows95根本就不是个多进程系统。到后来,我才知道,原来当然微软正从DOS的单进程往windows的多进程进发,而其主程序员实在太蹩脚,根本不懂多进程,所以windows95的多进程完全是个假的。当时,如果我知道有这个优先级队列的话,就不会去跟踪windows的内核了,也可以节约很多时间。
说到微软的蹩脚程序员,不得不说我当年学VC的经历。我学的时候VC还是4.0,后来做毕业设计的时候是6.0。和现在学Java一样,我花了很多时间去研究MFC的源代码,后来发现VC4.0的CD里面有个目录,里面的C/C++代码,非常的优美,让人赏心悦目;而其他的地方,写得还不如俺的一些同学。后来有人告诉我,我看的实际上是HP的代码,他们帮微软在做事儿。后来的VC6.0已经把HP的代码买下来,融入自己的了。不过就算如此,我还是发现,VC6.0的伪随机函数是错的。我曾直接联系他们的总部,报告random这个bug。98年的时候,我虽然已经开始用Internet好几年了,但是没有email,他们的态度倒是挺好,直接从西雅图邮国际信件给俺,说知道这个bug了,会处理的。不过后来,俺毕业后,开始的工作是汇编,然后是Java,再也没有碰过微软的东西,不知道他们bug是不是还是那么多,内部的source还是那么烂。


推荐阅读
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
author-avatar
用户d4k2wd8en1
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有